package com.example.framework509.common.aspects;

import com.example.framework509.common.annotation.AutoEntityProcess;
import com.example.framework509.common.entities.ResMsg;
import com.example.framework509.common.entities.utils.AnnotationInfo;
import com.example.framework509.common.entities.utils.ParameterInfo;
import com.example.framework509.common.entities.utils.RequestInfo;
import com.example.framework509.common.enums.EntityAutoOptEnum;
import com.example.framework509.common.exceptions.AnnotationException;
import com.example.framework509.common.exceptions.NotExistException;
import com.example.framework509.common.exceptions.ParamFieldException;
import com.example.framework509.common.exceptions.PermissionException;
import com.example.framework509.common.exceptions.base.BaseCustomException;
import com.example.framework509.common.services.Impl.AutoEntityServiceImpl;
import com.example.framework509.common.utils.JwtUtil;
import com.example.framework509.logCore.service.impl.LogServiceImpl;
import com.example.framework509.permissionCore.service.impl.LoginServiceImpl;
import com.example.framework509.permissionCore.service.impl.UserServiceImpl;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Aspect
@Component
public class AutoEntityProcessAspect {

    //service注入
    @Autowired
    protected ApplicationContext applicationContext;
    //编程事务
    @Autowired
    private TransactionTemplate transactionTemplate;
    //日志相关
    @Autowired
    private LogServiceImpl logService;
    @Autowired
    private UserServiceImpl userService;
    //权限验证相关
    @Autowired
    private LoginServiceImpl loginService;

    @Pointcut("@annotation(com.example.framework509.common.annotation.AutoEntityProcess)")
    public void aspectCut() {
    }

    @Around("aspectCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //日志部分起点
        Object result;
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        RequestInfo requestInfo = new RequestInfo();
        ParameterInfo parameterInfo = new ParameterInfo();
        AnnotationInfo annotationInfo = new AnnotationInfo();
        AutoEntityServiceImpl<?> entityBaseAutoService = initialAspectInfo(pjp, annotationInfo, requestInfo);
        try {
            //权限验证
            permissionVerify(annotationInfo, requestInfo);
            //获取参数信息
            getParameterInfo(pjp, parameterInfo, annotationInfo, requestInfo, entityBaseAutoService);
            //参数验证
            parameterVerify(parameterInfo, requestInfo);
            //业务处理
            result = businessProcess(pjp, entityBaseAutoService, parameterInfo, annotationInfo, requestInfo);
            //日志处理
            logService.saveLog(pjp, requestInfo.getRequest(), requestInfo.getRequestTime(), result, annotationInfo.getLOG_MSG(), "无");
            return result;
        } catch (Exception e) {
            //异常处理
            ResMsg resMsg;
            if (e instanceof BaseCustomException) {
                BaseCustomException baseCustomException = (BaseCustomException) e;
                resMsg = ResMsg.failResOneMes(baseCustomException.getMessage(), baseCustomException.getCode());
            } else {
                resMsg = ResMsg.failResOneMes(e.getMessage(), 1101);
            }
            //日志处理
            String errorMsg = e.getMessage() == null ? "no error msg" : e.getMessage().length() > 249 ? e.getMessage().substring(0, 249) : e.getMessage();
            logService.saveLog(pjp, requestInfo.getRequest(), requestInfo.getRequestTime(), resMsg, annotationInfo.getLOG_MSG(), errorMsg);
            throw e;
        }

    }

    private void permissionVerify(AnnotationInfo annotationInfo, RequestInfo requestInfo) {
        if (annotationInfo.isOPEN_PERMISSION()) {
            Integer userId = JwtUtil.getIdFromToken(requestInfo.getRequest());
            Integer permissionCode;
            switch (requestInfo.getRequestType()) {
                case "GET":
                    permissionCode = 5;
                    if (!annotationInfo.getPERMISSION_TYPE().contains("5")) throw new PermissionException("未开放GET权限");
                    break;
                case "POST":
                    permissionCode = 2;
                    if (!annotationInfo.getPERMISSION_TYPE().contains("5")) throw new PermissionException("未开放POST权限");
                    break;
                case "PUT":
                    permissionCode = 3;
                    if (!annotationInfo.getPERMISSION_TYPE().contains("5")) throw new PermissionException("未开放PUT权限");
                    break;
                case "DELETE":
                    permissionCode = 4;
                    if (!annotationInfo.getPERMISSION_TYPE().contains("5"))
                        throw new PermissionException("未开放DELETE权限");
                    break;
                default:
                    permissionCode = -1;
                    break;
            }
            List<Integer> userPerms = loginService.getUserPerms(userId, permissionCode);
            if (!userPerms.contains(annotationInfo.getMENU_CODE())) throw new PermissionException();
        }
    }

    private void getParameterInfo(ProceedingJoinPoint pjp, ParameterInfo parameterInfo, AnnotationInfo annotationInfo, RequestInfo requestInfo, AutoEntityServiceImpl<?> entityBaseAutoService) {
        if (!annotationInfo.isAUTO_EXECUTE()) return;
        try {
            //获取参数信息
            Map<String, Object> parameter = (Map<String, Object>) pjp.getArgs()[0];
            //if (parameter == null) return;
            //若是GET请求，则存为Map
            if (requestInfo.getRequestType().equals("GET")) {
                parameterInfo.setSearchMap(parameter);
            } else {
                Field[] declaredFields = annotationInfo.getBUSINESS_CLASS().getDeclaredFields();
                for (Field field : declaredFields) {
                    String fieldName = field.getName();
                    Annotation[] annotations = field.getDeclaredAnnotations();
                    for (Annotation annotation : annotations) {
                        String annotationName = annotation.annotationType().getSimpleName();
                        switch (annotationName) {
                            case "TableId":
                                parameterInfo.setId(parameter.get(fieldName));
                                break;
                            case "VerifyRepetitionField":
                                parameterInfo.addVerifyRepetitionFields(fieldName, parameter.get(fieldName));
                                break;
                            case "VerifyMultiRepetitionField":
                                parameterInfo.addVerifyMultiRepetitionFields(fieldName, parameter.get(fieldName));
                                break;
                            case "RequiredField":
                                if (fieldName.equals("creator") || fieldName.equals("editor")) {
                                    String userName = userService.getUserNameFromRequest(requestInfo.getRequest());
                                    parameterInfo.addRequiredFields(fieldName, userName);
                                    parameter.put(fieldName, userName);
                                } else {
                                    if (requestInfo.getRequestType().equals("POST") && parameter.get(fieldName) == null)
                                        throw new ParamFieldException(fieldName + "参数为null");
                                    parameterInfo.addRequiredFields(fieldName, parameter.get(fieldName));
                                }
                                break;
                            case "AllowNull":
                                parameterInfo.addAllowNullFields(fieldName, parameter.get(fieldName));
                                break;
                            case "EditKey":
                                parameterInfo.addEditKeys(fieldName, parameter.get(fieldName));
                                break;
                            default:
                                break;
                        }
                    }
                }
                //将map封装成业务实体对象
                parameter.remove("isDeleted");
                parameter.remove("version");
                parameterInfo.setBusinessEntity(entityBaseAutoService.createEntityFromMap(parameter));
            }
        } catch (Exception e) {
            throw new ParamFieldException("获取参数信息失败，caused by: " + e.getMessage());
        }
    }

    private void parameterVerify(ParameterInfo parameterInfo, RequestInfo requestInfo) {
        Map<String, Object> allowNullFields = parameterInfo.getAllowNullFields();
        switch (requestInfo.getRequestType()) {
            case "GET":
                break;
            case "POST":
                for (String key : parameterInfo.getRequiredFields().keySet()) {
                    if (!allowNullFields.containsKey(key) && parameterInfo.getRequiredFields().get(key) == null)
                        throw new ParamFieldException("参数非空字段为null");
                }
                break;
            case "PUT":
                //检验用来编辑的标识字段存在且唯一
                Map<String, Object> editKeys = parameterInfo.getEditKeys();
                if (editKeys.size() == 1) {
                    if (!parameterInfo.getVerifyRepetitionFields().containsKey(editKeys.keySet().iterator().next()))
                        throw new ParamFieldException("@Edit字段不是唯一标识字段");
                    if (editKeys.containsValue(null)) throw new ParamFieldException("@Edit字段为null");
                } else if (editKeys.size() > 1) {
                    if (!(parameterInfo.getVerifyMultiRepetitionFields().keySet().containsAll(editKeys.keySet()) && parameterInfo.getVerifyMultiRepetitionFields().keySet().containsAll(editKeys.keySet())))
                        throw new ParamFieldException("@Edit字段不是组合唯一标识字段");
                    if (editKeys.containsValue(null))
                        throw new ParamFieldException("组合唯一标识字段不完整");
                } else {
                    if (parameterInfo.getId() == null) throw new ParamFieldException("无@Edit标识且id为null");
                }
                //检查除@Edit字段外的@RequiredField字段不能全为null
                boolean isAllNull = true;
                Map<String, Object> requiredFields = parameterInfo.getRequiredFields();
                for (String key : requiredFields.keySet()) {
                    if (editKeys.containsKey(key)) continue;
                    if (requiredFields.get(key) != null) isAllNull = false;
                }
                if (isAllNull) throw new ParamFieldException("参数中无可更改的字段");
                break;
            case "DELETE":
                //检查有效标识字段的数量
                int validIdentifyFieldNum = 0;
                if (parameterInfo.getId() != null) validIdentifyFieldNum += 1;
                validIdentifyFieldNum += parameterInfo.getVerifyRepetitionFields().values().stream().filter(Objects::nonNull).count();
                if (parameterInfo.getVerifyMultiRepetitionFields().size() != 0 && !parameterInfo.getVerifyMultiRepetitionFields().containsValue(null))
                    validIdentifyFieldNum += 1;
                if (validIdentifyFieldNum == 0) throw new ParamFieldException("参数缺少标识字段");
                else if (validIdentifyFieldNum > 1) throw new ParamFieldException("参数含有多个标识字段");
                break;
            default:
                throw new ParamFieldException("未知请求类型");
        }
    }

    private Object businessProcess(ProceedingJoinPoint pjp, AutoEntityServiceImpl<?> entityBaseAutoService, ParameterInfo parameterInfo, AnnotationInfo annotationInfo, RequestInfo requestInfo) throws Throwable {
        switch (requestInfo.getRequestType()) {
            case "GET":
                return processRequest(pjp, entityBaseAutoService, EntityAutoOptEnum.SearchByMap, parameterInfo, annotationInfo);
            case "POST":
                return processRequest(pjp, entityBaseAutoService, EntityAutoOptEnum.CreateAutoId, parameterInfo, annotationInfo);
            case "PUT":
                if (parameterInfo.getEditKeys().size() == 0)
                    return processRequest(pjp, entityBaseAutoService, EntityAutoOptEnum.EditById, parameterInfo, annotationInfo);
                else
                    return processRequest(pjp, entityBaseAutoService, EntityAutoOptEnum.EditByParam, parameterInfo, annotationInfo);
            case "DELETE":
                return processRequest(pjp, entityBaseAutoService, EntityAutoOptEnum.DeleteByParam, parameterInfo, annotationInfo);
            default:
                throw new ParamFieldException("未知请求类型");
        }
    }

    //region ########################################### 内部调用 ######################################

    private AutoEntityServiceImpl<?> initialAspectInfo(ProceedingJoinPoint pjp, AnnotationInfo annotationInfo, RequestInfo requestInfo) {
        //获取请求信息
        requestInfo.setRequest(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
        requestInfo.setRequestTime(System.currentTimeMillis());
        requestInfo.setRequestType(requestInfo.getRequest().getMethod());
        //获取注解信息
        AutoEntityProcess autoAnnotation = ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(AutoEntityProcess.class);
        if (autoAnnotation.AUTO_EXECUTE() && autoAnnotation.BUSINESS_CLASS().equals(Object.class))
            throw new AnnotationException("Controller注解未指定业务实体类");
        annotationInfo.setBUSINESS_CLASS(autoAnnotation.BUSINESS_CLASS());
        annotationInfo.setLOG_MSG(autoAnnotation.LOG_MSG());
        annotationInfo.setCUSTOM_EXECUTE(autoAnnotation.CUSTOM_EXECUTE());
        annotationInfo.setAUTO_EXECUTE(autoAnnotation.AUTO_EXECUTE());
        annotationInfo.setOPEN_PERMISSION(autoAnnotation.MENU_CODE() != -1);
        annotationInfo.setMENU_CODE(autoAnnotation.MENU_CODE());
        annotationInfo.setPERMISSION_TYPE(autoAnnotation.PERMISSION_TYPE());
        //注入service impl
        String entityPath = annotationInfo.getBUSINESS_CLASS().getName();
        String simpleName = annotationInfo.getBUSINESS_CLASS().getSimpleName();
        String servicePath = entityPath.replace("entity", "service.impl")
                .replace(simpleName, simpleName + "ServiceImpl");
        if (!annotationInfo.isAUTO_EXECUTE()) return null;
        Class<?> serviceClass;
        try {
            serviceClass = Class.forName(servicePath);
        } catch (ClassNotFoundException e) {
            throw new NotExistException(servicePath + " not exist");
        }
        return (AutoEntityServiceImpl<?>) applicationContext.getBean(serviceClass);
    }

    private Object processRequest(ProceedingJoinPoint pjp, AutoEntityServiceImpl<?> entityBaseAutoService, EntityAutoOptEnum opt, ParameterInfo parameterInfo, AnnotationInfo annotationInfo) throws Throwable {
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        Object execute = transactionTemplate.execute(new TransactionCallback() {
            @Override
            public Object doInTransaction(TransactionStatus transactionStatus) {
                ResMsg resMsg = ResMsg.failResOneMes("no action to do", 1101);
                try {
                    if (annotationInfo.isAUTO_EXECUTE()) switch (opt) {
                        case SearchByMap:
                            resMsg = entityBaseAutoService.baseQuerySearchByMap(parameterInfo.getSearchMap());
                            break;
                        case CreateAutoId:
                            resMsg = entityBaseAutoService.baseQueryAdd(parameterInfo.getBusinessEntity());
                            break;
                        case EditById:
                            resMsg = entityBaseAutoService.baseQueryEditByParam(parameterInfo.getBusinessEntity(), true);
                            break;
                        case EditByParam:
                            resMsg = entityBaseAutoService.baseQueryEditByParam(parameterInfo.getBusinessEntity(), false);
                            break;
                        case DeleteByParam:
                            resMsg = entityBaseAutoService.baseQueryDelOneByParam(parameterInfo.getBusinessEntity());
                            break;
                    }
                    if (annotationInfo.isCUSTOM_EXECUTE()) {
                        Object proceed = pjp.proceed();
                        if (proceed instanceof Throwable) throw (Throwable) proceed;
                        if (!annotationInfo.isAUTO_EXECUTE()) return proceed;
                    }
                    return resMsg;
                } catch (Throwable e) {
                    transactionStatus.setRollbackOnly();
                    return e;
                }
            }
        });
        if (execute instanceof Throwable) throw (Throwable) execute;
        return execute;
    }

    //endregion

}
